#include "httplib.h"


#if defined(__WINDOWS__)
/* windows platform */
#include <winsock2.h>
#else
/* unix platform */
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
/* todo */
#endif

#include <stdio.h>
#include <iostream>
#include <wx/log.h>

#include "httplib.h"
#include "tinyhttp/http.h"

using namespace httplib;

// tiny http stuff //////////////////////////

static void* responseRealloc(void* opaque, void* ptr, int size)
{
    return realloc(ptr, size);
}

static void responseBody(void* opaque, const char* data, int size)
{
    HttpResponse* response = (HttpResponse*)opaque;
    response->setBody(data, size);
}

static void responseHeader(void* opaque, const char* ckey, int nkey, const char* cvalue, int nvalue)
{
    HttpResponse* response = (HttpResponse*)opaque;
    wxString name(ckey, nkey);
    wxString value(cvalue, nvalue);
    response->addHeader(name, value);
}

static void responseCode(void* opaque, int code)
{
    HttpResponse* response = (HttpResponse*)opaque;
    response->setStatusCode(code);
}

static const http_funcs responseFuncs = {
    responseRealloc,
    responseBody,
    responseHeader,
    responseCode,
};

// HttpHeader //////////////////////////
void HttpHeader::writeRaw(std::vector<char> &buffer)
{
    wxCharBuffer b = (wxString::Format(wxT("%s: %s"), mName, mValue)).ToAscii();
    buffer.insert(buffer.end(), b.data(), b.data() + b.length());
    buffer.push_back('\015');
    buffer.push_back('\012');
}

// HttpMessage ///////////////////////////////////////////
HttpMessage::HttpMessage()
{
    setContentType(wxT("text/html"));
}

const HttpHeader &HttpMessage::getHeader(const int index) const
{
    return mHeaders.at(index);
};

void HttpMessage::writeRaw(std::vector<char> &buffer)
{
    // first line
    writeRawFirstLine(buffer);

    // add headers
    for (std::vector<HttpHeader>::iterator it = mHeaders.begin() ; it != mHeaders.end(); ++it)
        it->writeRaw(buffer);

    // separate headers and body
    buffer.push_back('\015');
    buffer.push_back('\012');

    // add body
    if (mBody.size() > 0)
        buffer.insert(buffer.end(), mBody.begin(), mBody.end());
}

void HttpMessage::setContentType(const wxString &contentType)
{
    setHeaderValue(wxT("Content-type"), contentType);
}

void HttpMessage::setHeaderValue(const wxString &name, const wxString &value)
{
    // look for user agent header
    bool found = false;
    for (std::vector<HttpHeader>::iterator it = mHeaders.begin() ; it != mHeaders.end(); ++it)
    {
        HttpHeader h = *it;
        if (h.getName().CmpNoCase(name) == 0)
        {
            h.setValue(value);
            found = true;
            break;
        }
    }
    if (!found)
        mHeaders.push_back(HttpHeader(name, value));
}

void HttpMessage::addHeader(const wxString &name, const wxString &value)
{
    mHeaders.push_back(HttpHeader(name, value));
}

// HttpRequest ///////////////////////////////////////////

HttpRequest::HttpRequest(const wxURL &url)
    : mMethod(wxT("GET")), mUrl(url)
{
    setUserAgent(wxT("HttpLib"));
}

void HttpRequest::writeRawFirstLine(std::vector<char> &buffer)
{
    // create header
    wxString headerStr = wxString::Format(wxT("%s /%s HTTP/1.0"),
        getMethod(), getUrl().GetPath());
    wxCharBuffer b = headerStr.ToAscii();
    buffer.insert(buffer.end(), b.data(), b.data() + b.length());
    //msgRaw.AppendData(headerBuffer, headerBuffer.length());
    buffer.push_back('\015');
    buffer.push_back('\012');
}

void HttpRequest::setUserAgent(const wxString &userAgent)
{
    setHeaderValue(wxT("User-agent"), userAgent);
}

 // HttpLib //////////////////////////////////////////////

HttpResponse *HttpLib::sendRequest(HttpRequest &request)
{
    //wxLogMessage(wxT("Sending HTTP request to %s"), request.getUrl().GetURL());

    std::vector<char> v;
    v.reserve(1024);
    request.writeRaw(v);
    /*wxLogMessage(wxT("Request has %d bytes"), v.size());
    for (std::vector<char>::iterator it = v.begin() ; it != v.end(); ++it)
    {
        std::cout << *it;
    }
    std::cout << std::endl;*/

    int     s;
    struct  hostent *hp;
    struct  sockaddr_in server;

    /* get host info by name :*/
    if ((hp = gethostbyname(request.getUrl().GetServer().mb_str(wxConvUTF8))))
    {
        memset((char *) &server,0, sizeof(server));
        memmove((char *) &server.sin_addr, hp->h_addr, hp->h_length);
        server.sin_family = hp->h_addrtype;
        server.sin_port = (unsigned short) htons(80);
    }
    else
        throw HttpException(wxT("Invalid server name"));

    // create socket
    if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0)
        throw HttpException(wxT("Cannot create client socket"));
    setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, 0, 0);

    // connect to server
    if (connect(s, (struct sockaddr *)&server, sizeof(server)) < 0)
        throw HttpException(wxT("Cannot connect to socket"));

    // send request data
    unsigned int writtenBytes = send(s, &v[0], v.size(), 0);
    if (writtenBytes != v.size())
        throw HttpException(wxT("Cannot write reqest data to client socket"));

    std::vector<char> responseRaw;
    char bufferRecv[1024];
    while (true)
    {
        int readBytes = recv(s, bufferRecv, 1024, 0);
        //std::cout << "Read bytes: " << readBytes << std::endl;
        if (readBytes == 0)
        {
            break;
        }
        else if (readBytes < 0)
        {
            close(s);
            throw HttpException(wxT("Error reading from client socket"));
        }
        // append read data to response buffer
        responseRaw.insert(responseRaw.end(), bufferRecv, bufferRecv + readBytes);
    }

    // close socket
    close(s);

    //wxLogMessage(wxT("Response dump (%d bytes):"), responseRaw.size());
    /*for (std::vector<char>::iterator it = responseRaw.begin() ; it != responseRaw.end(); ++it)
    {
        std::cout << *it;
    }
    std::cout << std::endl;*/

    // parse response
    //wxLogMessage(wxT("Parsing http response of size %d"), responseRaw.size());
    HttpResponse *response = new HttpResponse();
    http_roundtripper rt;
    http_init(&rt, responseFuncs, response);

    bool needMore = true;
    unsigned int consumed = 0;
    while (needMore && consumed < responseRaw.size())
    {
        int read = 0;
        needMore = http_data(&rt, &responseRaw[consumed], responseRaw.size(), &read);
        consumed += read;
    }

    if (http_iserror(&rt))
    {
        http_free(&rt);
        throw HttpException(wxT("Error parsing http data"));
    }

    http_free(&rt);

    return response;
}



